using System.Collections.Generic;
using System.Linq;
using Tycho.Parser.Peg;

namespace Tycho.Grammar {
    public class RuleCompiler : IRuleCompiler {
        private readonly IProductionCompiler ProductionCompiler;
        private readonly IRuleCompiler DelimitedRuleCompiler;
        private readonly IMultipleRuleCompiler MultipleRuleCompiler;
        private readonly IRuleCompiler SequenceRuleCompiler;

        public RuleCompiler (IProductionCompiler productionCompiler) {
            ProductionCompiler = productionCompiler;
            DelimitedRuleCompiler = new DelimitedRuleCompiler (this);
            MultipleRuleCompiler = new MultipleRuleCompiler (this);
            SequenceRuleCompiler = new SequenceRuleCompiler (this);
        }

        public IRule Compile (ITerm term, ProductionCompilerContext context, ICaptureCounter captureCounter) {
            var compositeTerm = term as CompositeTerm;

            if (compositeTerm != null) {
                switch (compositeTerm.Name) {
                    case "choice":
                        return CompileChoiceRule (((ListTerm) compositeTerm.SubTerms["choices"]).Terms, context, captureCounter);
                    case "keyword":
                        return CompileKeywordRule (((StringTerm) compositeTerm.SubTerms ["name"]).Value);
                    case "sequence":
                        return SequenceRuleCompiler.Compile (term, context, captureCounter);
                    case "optional":
                        return CompileOptionalRule (compositeTerm.SubTerms["rule"], context, captureCounter);
                    case "zero-or-more":
                        return CompileMultipleRule (0, -1, compositeTerm, context, captureCounter);
                    case "one-or-more":
                        return CompileMultipleRule (1, -1, compositeTerm, context, captureCounter);
                    case "sub-rule":
                        return Compile (compositeTerm.SubTerms["rule"], context, captureCounter);
                    case "delimited":
                        return DelimitedRuleCompiler.Compile (compositeTerm, context, captureCounter);
                    case "capture":
                        return CompileCapture (compositeTerm, context, captureCounter);
                }
            } else if (term is IdentifierTerm) {
                return new LiteralRule (context.Lookup.Lookup (((IdentifierTerm) term).Name));
            }

            throw new RuleNotRecognisedException ();
        }

        private Rule CompileCapture (CompositeTerm term, ProductionCompilerContext context, ICaptureCounter captureCounter) {
            var name = ((IdentifierTerm) term.SubTerms["name"]).Name;

            IProduction captureProduction;

            ITerm productionTerm;
            if (term.SubTerms.TryGetValue ("production", out productionTerm)) {
                captureProduction = ProductionCompiler.Compile (productionTerm, null, context).Production;
            } else {
                captureProduction = context.Environment.Default;
            }

            captureCounter.Add (name);

            return new NamedRule (name, captureProduction);
        }

        private IRule CompileMultipleRule (int minimum, int maximum, ITerm term, ProductionCompilerContext context, ICaptureCounter captureCounter) {
            return MultipleRuleCompiler.Compile (minimum, maximum, term, context, captureCounter);
        }

        private Rule CompileOptionalRule (ITerm rule, ProductionCompilerContext context, ICaptureCounter captureCounter) {
            return new OptionalRule (Compile (rule, context, captureCounter));
        }

        private static Rule CompileKeywordRule (string keyword) {
            return new KeywordRule (keyword);
        }

        private Rule CompileChoiceRule (IEnumerable<ITerm> choices, ProductionCompilerContext context, ICaptureCounter captureCounter) {
            return new ChoiceRule (choices.Select (c => Compile (c, context, captureCounter)).ToArray ());
        }
    }
}